#ifndef MESSAGE_PACK_WRITER_H
#define MESSAGE_PACK_WRITER_H
#include <memory>
#include <span>
#include <string>
#include <boost/endian/conversion.hpp>
#include "../util/concepts.h"
#include "../../jtt809_logger.h"
#include "../exception/jtt809_exception.h"


namespace protocol {
    class MessagePackWriter {
        unsigned char *buffer;
        unsigned char *raw;
        int writePosition = 0;
        int rawPosition = 0;
        size_t bufferSize;

        bool tryEncode(const uint8_t& to_encode, uint16_t& byte) const;
    public:
        explicit MessagePackWriter(const size_t& size) : bufferSize(size) {
            this->buffer = new unsigned char[size];
            this->raw = new unsigned char[size];
        }
        void writeByte(uint8_t byte, bool encode=true);
        template<size_t N>
        void writeBytes(uint8_t (&bytes)[N]);
        void writeBytes(const uint8_t *bytes, size_t len);
        template<UnsignedInt uint>
        void writeUint(uint data);
        template<size_t N>
        void writeString(char (&)[N]);
        void writeTime(time_t time);
        void writeCrcCodeAndEndFlag(uint8_t end);
        template<typename T>
        void writePointer(T *data, size_t size) {
            ERR(JTT809Logger::logger(), "type not support!");
            throw JTT809Exception(JTT809ErrorCode::WrongFlag, "type not support");
        }
        template<>
        void writePointer<char>(char *data, size_t size) {
            const auto strlen = std::strlen(data);
            for (int i = 0; i < strlen; ++i) {
                if (uint16_t to_write; tryEncode(data[i], to_write)) {
                    memcpy(buffer+writePosition++, &to_write, sizeof(to_write));
                    writePosition++;
                }
                else
                    memcpy(buffer+writePosition++, data+i, sizeof(char));
                memcpy(raw+rawPosition++, data+i, sizeof(char));
            }
            const auto fill_size = size - strlen;
            for (int i = 0; i < fill_size; ++i) {
                buffer[writePosition++] = 0x00;
                raw[rawPosition++] = 0x00;
            }
        }
        template<>
        void writePointer<uint8_t>(uint8_t *data, size_t size) {
            writeBytes(data, size);
        }

        [[nodiscard]] std::shared_ptr<char[]> flushAndGet() const;
        [[nodiscard]] size_t size() const;

        void combine(const MessagePackWriter& writer);

        ~MessagePackWriter();
    };

    template<size_t N>
    void MessagePackWriter::writeBytes(uint8_t(&bytes)[N]) {
        for (int i = 0; i < N; ++i) {
            const auto byte = bytes[i];
            writeByte(byte);
        }
    }

    template<UnsignedInt uint>
    void MessagePackWriter::writeUint(uint data) {
        auto net = boost::endian::native_to_big(data);
        memcpy(buffer+writePosition, &net, sizeof(uint));
        memcpy(raw+rawPosition, &net, sizeof(uint));
        rawPosition += sizeof(uint);
        writePosition += sizeof(uint);
    }

    template<size_t N>
    void MessagePackWriter::writeString(char(&str)[N]) {
        auto strlen = std::strlen(str);
        for (int i = 0; i < strlen; ++i) {
            if (uint16_t to_write; tryEncode(str[i], to_write)) {
                memcpy(buffer+writePosition++, &to_write, sizeof(to_write));
                writePosition++;
            }
            else
                memcpy(buffer+writePosition++, str+i, sizeof(char));
            memcpy(raw+rawPosition++, str+i, sizeof(char));
        }
        auto fill_size = N - strlen;
        for (int i = 0; i < fill_size; ++i) {
            buffer[writePosition++] = 0x00;
            raw[rawPosition++] = 0x00;
        }
    }
}
#endif // !MESSAGE_PACK_WRITER_H
